{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "76abc954",
   "metadata": {},
   "source": [
    "# Hugging Face Local Pipelines\n",
    "\n",
    "`HuggingFacePipeline` 클래스를 통해 Hugging Face 모델을 로컬에서 실행할 수 있습니다.\n",
    "\n",
    "[Hugging Face Model Hub](https://huggingface.co/models)는 온라인 플랫폼에서 120,000개 이상의 모델, 20,000개의 데이터셋, 50,000개의 데모 앱(Spaces)을 호스팅하며, 모두 오픈 소스이고 공개적으로 사용 가능하여 사람들이 쉽게 협업하고 함께 ML을 구축할 수 있습니다.\n",
    "\n",
    "이러한 모델은 LangChain에서 이 로컬 파이프라인 래퍼를 통해 호출하거나, HuggingFaceHub 클래스를 통해 호스팅된 추론 엔드포인트를 호출하여 사용할 수 있습니다. 호스팅된 파이프라인에 대한 자세한 내용은 [HuggingFaceHub](./huggingface_hub) 노트북을 참조하세요.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "730bb136",
   "metadata": {},
   "source": [
    "사용하기 위해서는 [PyTorch](https://pytorch.org/get-started/locally/)와 함께 Python [패키지 transformers](https://pypi.org/project/transformers/)가 설치되어 있어야 합니다.\n",
    "\n",
    "또한, 보다 메모리 효율적인 attention 구현을 위해 `xformer`를 설치할 수도 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f19f956",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 설치\n",
    "# !pip install -qU transformers"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a35a7a9c",
   "metadata": {},
   "source": [
    "모델을 다운로드 받을 경로를 설정\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ebb4f6d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 허깅페이스 모델/토크나이저를 다운로드 받을 경로\n",
    "import os\n",
    "\n",
    "# ./cache/ 경로에 다운로드 받도록 설정\n",
    "os.environ[\"HF_HOME\"] = \"./cache/\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb8a857e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "from langchain_teddynote.messages import stream_response\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH04-Models\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd955579",
   "metadata": {},
   "source": [
    "### Model Loading\n",
    "\n",
    "모델은 `from_model_id` 메서드를 사용하여 모델 매개변수를 지정함으로써 로드할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09b5cf5d",
   "metadata": {},
   "source": [
    "- `HuggingFacePipeline` 클래스를 사용하여 Hugging Face의 사전 학습된 모델을 로드합니다.\n",
    "- `from_model_id` 메서드를 사용하여 `beomi/llama-2-ko-7b` 모델을 지정하고, 작업을 \"text-generation\"으로 설정합니다.\n",
    "- `pipeline_kwargs` 매개변수를 사용하여 생성할 최대 토큰 수를 10으로 제한합니다.\n",
    "- 로드된 모델은 `hf` 변수에 할당되며, 이를 통해 텍스트 생성 작업을 수행할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "562184a1",
   "metadata": {},
   "source": [
    "사용한 모델: https://huggingface.co/beomi/llama-2-ko-7b\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50628f0e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_huggingface import HuggingFacePipeline\n",
    "\n",
    "# HuggingFace 모델을 다운로드 받습니다.\n",
    "hf = HuggingFacePipeline.from_model_id(\n",
    "    model_id=\"beomi/llama-2-ko-7b\",  # 사용할 모델의 ID를 지정합니다.\n",
    "    task=\"text-generation\",  # 수행할 작업을 지정합니다. 여기서는 텍스트 생성입니다.\n",
    "    # 파이프라인에 전달할 추가 인자를 설정합니다. 여기서는 생성할 최대 토큰 수를 10으로 제한합니다.\n",
    "    pipeline_kwargs={\"max_new_tokens\": 512},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "486a2488",
   "metadata": {},
   "source": [
    "기존의 `transformers` pipeline을 직접 전달하여 로드할 수도 있습니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e282bb6a",
   "metadata": {},
   "source": [
    "HuggingFacePipeline을 사용하여 텍스트 생성 모델을 구현합니다.\n",
    "\n",
    "- `AutoTokenizer`와 `AutoModelForCausalLM`을 사용하여 `beomi/llama-2-ko-7b` 모델과 토크나이저를 로드합니다.\n",
    "- `pipeline` 함수를 사용하여 \"text-generation\" 파이프라인을 생성하고, 모델과 토크나이저를 설정합니다. 최대 생성 토큰 수는 10으로 제한합니다.\n",
    "- `HuggingFacePipeline` 클래스를 사용하여 `hf` 객체를 생성하고, 생성된 파이프라인을 전달합니다.\n",
    "\n",
    "이렇게 생성된 `hf` 객체를 사용하여 주어진 프롬프트에 대한 텍스트 생성을 수행할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "201d4f17",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_huggingface import HuggingFacePipeline\n",
    "from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline\n",
    "\n",
    "# huggingface repository에 등록된 모델 ID를 지정합니다.\n",
    "model_id = \"microsoft/Phi-3-mini-4k-instruct\"\n",
    "\n",
    "# 토크나이저를 로드합니다.\n",
    "tokenizer = AutoTokenizer.from_pretrained(model_id)\n",
    "\n",
    "# 지정된 모델을 로드합니다.\n",
    "model = AutoModelForCausalLM.from_pretrained(\n",
    "    model_id,\n",
    "    # load_in_4bit=True, # bitsandbytes 설치되어 있는 경우(linux)\n",
    "    # attn_implementation=\"flash_attention_2\", # ampere GPU 가 있는 경우\n",
    ")\n",
    "\n",
    "# 파이프라인을 생성합니다.\n",
    "pipe = pipeline(\"text-generation\", model=model, tokenizer=tokenizer, max_new_tokens=512)\n",
    "\n",
    "# HuggingFacePipeline 객체를 생성하고, 생성된 파이프라인을 전달합니다.\n",
    "hf = HuggingFacePipeline(pipeline=pipe)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86d743ff",
   "metadata": {},
   "source": [
    "### Gated Model 의 사용\n",
    "\n",
    "Gated Model 은 HuggingFace 에서 라이센스 동의 하에 이용할 수 있는 모델입니다.\n",
    "\n",
    "사전에 모델 페이지에 접속하여 동의하신 후, Hugging Face 토큰을 발급 받아야 합니다.\n",
    "\n",
    "아래는 Gated 모델을 사용하기 위한 예제입니다. HuggingFace 에서 발급받은 토큰을 아래와 같이 지정해 주어야 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "863d96ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_huggingface import HuggingFacePipeline\n",
    "from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline\n",
    "\n",
    "# huggingface repository에 등록된 모델 ID를 지정합니다.\n",
    "model_id = \"google/gemma-7b-it\"\n",
    "\n",
    "# 이곳에 발급 받은 Hugging Face 토큰을 입력합니다.\n",
    "your_huggingface_token = \"hf_............................\"\n",
    "\n",
    "# 토크나이저를 로드합니다.\n",
    "tokenizer = AutoTokenizer.from_pretrained(model_id, token=your_huggingface_token)\n",
    "\n",
    "# 지정된 모델을 로드합니다.\n",
    "model = AutoModelForCausalLM.from_pretrained(\n",
    "    model_id,\n",
    "    token=your_huggingface_token,\n",
    "    # load_in_4bit=True, # bitsandbytes 설치되어 있는 경우(linux)\n",
    "    # attn_implementation=\"flash_attention_2\", # ampere GPU 가 있는 경우\n",
    ")\n",
    "\n",
    "# 파이프라인을 생성합니다.\n",
    "pipe = pipeline(\"text-generation\", model=model, tokenizer=tokenizer, max_new_tokens=512)\n",
    "\n",
    "# HuggingFacePipeline 객체를 생성하고, 생성된 파이프라인을 전달합니다.\n",
    "hf_llm = HuggingFacePipeline(pipeline=pipe)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82ba2078",
   "metadata": {},
   "source": [
    "실행 및 결과 확인"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "be2f012c",
   "metadata": {},
   "outputs": [],
   "source": [
    "for token in hf_llm.stream(\"대한민국의 수도는 어디야?\"):\n",
    "    print(token, end=\"\", flush=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d7e2005c",
   "metadata": {},
   "source": [
    "### Create Chain\n",
    "\n",
    "모델이 메모리에 로드되면 프롬프트와 함께 구성하여 체인을 형성할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be0486e1",
   "metadata": {},
   "source": [
    "- `PromptTemplate` 클래스를 사용하여 질문과 답변 형식을 정의하는 프롬프트 템플릿을 생성합니다.\n",
    "- `prompt` 객체와 `hf` 객체를 파이프라인으로 연결하여 `chain` 객체를 생성합니다.\n",
    "- `chain.invoke()` 메서드를 호출하여 주어진 질문에 대한 답변을 생성하고 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07b03fd1",
   "metadata": {},
   "outputs": [],
   "source": [
    "from huggingface_hub import login\n",
    "\n",
    "login()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73732b74",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_huggingface import ChatHuggingFace\n",
    "\n",
    "llm = ChatHuggingFace(llm=hf)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a968a943",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "template = \"\"\"<|system|>You are a helpful assistant.<|end|>\n",
    "<|user|>{question}<|end|>\n",
    "<|assistant|>\"\"\"  # 질문과 답변 형식을 정의하는 템플릿\n",
    "\n",
    "prompt = PromptTemplate.from_template(template)  # 템플릿을 사용하여 프롬프트 객체 생성\n",
    "\n",
    "# 프롬프트와 언어 모델을 연결하여 체인 생성\n",
    "chain = prompt | llm | StrOutputParser()\n",
    "\n",
    "question = \"대한민국의 수도는 어디야?\"  # 질문 정의\n",
    "\n",
    "# print(\n",
    "# chain.invoke({\"question\": question})\n",
    "# )  # 체인을 호출하여 질문에 대한 답변 생성 및 출력\n",
    "\n",
    "answer = chain.stream({\"question\": question})\n",
    "stream_response(answer)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0cf89ff",
   "metadata": {},
   "source": [
    "### GPU Inference\n",
    "\n",
    "GPU에서 실행할 때는 `device=n` 매개변수를 지정하여 모델을 특정 디바이스에 배치할 수 있습니다.\n",
    "\n",
    "기본값은 `-1`로, CPU에서 추론을 수행합니다.\n",
    "\n",
    "여러 개의 GPU가 있거나 모델이 단일 GPU에 비해 너무 큰 경우에는 `device_map=\"auto\"`를 지정할 수 있습니다.\n",
    "\n",
    "이 경우 [Accelerate](https://huggingface.co/docs/accelerate/index) 라이브러리가 필요하며, 모델 가중치를 어떻게 로드할지 자동으로 결정하는 데 사용됩니다.\n",
    "\n",
    "_주의_: `device`와 `device_map`은 함께 지정해서는 안 되며, 예기치 않은 동작을 유발할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d82bbd3",
   "metadata": {},
   "source": [
    "- `HuggingFacePipeline`을 사용하여 `gpt2` 모델을 로드하고, `device` 매개변수를 0으로 설정하여 GPU에서 실행되도록 합니다.\n",
    "- `pipeline_kwargs` 매개변수를 사용하여 생성할 최대 토큰 수를 10으로 제한합니다.\n",
    "- `prompt`와 `gpu_llm`을 파이프라인으로 연결하여 `gpu_chain`을 생성합니다.\n",
    "- `gpu_chain.invoke()` 메서드를 호출하여 주어진 질문에 대한 답변을 생성하고 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a2b73a01",
   "metadata": {},
   "outputs": [],
   "source": [
    "gpu_llm = HuggingFacePipeline.from_model_id(\n",
    "    model_id=\"beomi/llama-2-ko-7b\",  # 사용할 모델의 ID를 지정합니다.\n",
    "    task=\"text-generation\",  # 수행할 작업을 설정합니다. 여기서는 텍스트 생성입니다.\n",
    "    # 사용할 GPU 디바이스 번호를 지정합니다. \"auto\"로 설정하면 accelerate 라이브러리를 사용합니다.\n",
    "    device=0,\n",
    "    # 파이프라인에 전달할 추가 인자를 설정합니다. 여기서는 생성할 최대 토큰 수를 10으로 제한합니다.\n",
    "    pipeline_kwargs={\"max_new_tokens\": 64},\n",
    ")\n",
    "\n",
    "gpu_chain = prompt | gpu_llm  # prompt와 gpu_llm을 연결하여 gpu_chain을 생성합니다.\n",
    "\n",
    "# 프롬프트와 언어 모델을 연결하여 체인 생성\n",
    "gpu_chain = prompt | gpu_llm | StrOutputParser()\n",
    "\n",
    "question = \"대한민국의 수도는 어디야?\"  # 질문 정의\n",
    "\n",
    "# 체인을 호출하여 질문에 대한 답변 생성 및 출력\n",
    "print(gpu_chain.invoke({\"question\": question}))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed01d23e",
   "metadata": {},
   "source": [
    "### Batch GPU Inference\n",
    "\n",
    "GPU 장치에서 실행하는 경우, 배치 모드로 GPU에서 추론을 실행할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db636da0",
   "metadata": {},
   "source": [
    "- `HuggingFacePipeline`을 사용하여 `beomi/llama-2-ko-7b` 모델을 로드하고, GPU에서 실행되도록 설정합니다.\n",
    "- `gpu_llm`을 생성할 때 `batch_size`를 2로 설정하고, `temperature`를 0으로, `max_length`를 64로 설정합니다.\n",
    "- `prompt`와 `gpu_llm`을 파이프라인으로 연결하여 `gpu_chain`을 생성하고, 종료 토큰을 \"\\n\\n\"로 설정합니다.\n",
    "- `gpu_chain.batch()`를 사용하여 `questions`의 질문들에 대한 답변을 병렬로 생성합니다.\n",
    "- 생성된 답변을 반복문을 통해 출력합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eefc7f18",
   "metadata": {},
   "outputs": [],
   "source": [
    "gpu_llm = HuggingFacePipeline.from_model_id(\n",
    "    model_id=\"beomi/llama-2-ko-7b\",  # 사용할 모델의 ID를 지정합니다.\n",
    "    task=\"text-generation\",  # 수행할 작업을 설정합니다.\n",
    "    device=0,  # GPU 디바이스 번호를 지정합니다. -1은 CPU를 의미합니다.\n",
    "    batch_size=2,  # 배치 크기s를 조정합니다. GPU 메모리와 모델 크기에 따라 적절히 설정합니다.\n",
    "    model_kwargs={\n",
    "        \"temperature\": 0,\n",
    "        \"max_length\": 256,\n",
    "    },  # 모델에 전달할 추가 인자를 설정합니다.\n",
    ")\n",
    "\n",
    "# 프롬프트와 언어 모델을 연결하여 체인을 생성합니다.\n",
    "gpu_chain = prompt | gpu_llm.bind(stop=[\"\\n\\n\"])\n",
    "\n",
    "questions = []\n",
    "for i in range(4):\n",
    "    # 질문 리스트를 생성합니다.\n",
    "    questions.append({\"question\": f\"숫자 {i} 이 한글로 뭐에요?\"})\n",
    "\n",
    "answers = gpu_chain.batch(questions)  # 질문 리스트를 배치 처리하여 답변을 생성합니다.\n",
    "for answer in answers:\n",
    "    print(answer)  # 생성된 답변을 출력합니다."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
